##########################################
## Copyright 2012-2013 Ceruti Francesco & contributors
##
## This file is part of LiSP (Linux Show Player).
##########################################

from threading import Thread
from time import sleep
from ast import literal_eval

from PyQt4.QtCore import pyqtSignal, QObject

from lisp.midi import pypm  # @UnresolvedImport


def midiDevicesList():
    devices = []
    for d in range(pypm.CountDevices()):  # @UnusedVariable
        info = [i for i in pypm.GetDeviceInfo(d)]
        info[0] = info[0].decode("utf-8")           # MIDI API name (str)
        info[1] = info[1].decode("utf-8")           # MIDI Device name (str)
        #If default device
        if(d == pypm.GetDefaultInputDeviceID()):
            info[1] += ' (default)'
        info[2] = info[2] == 1                      # Is input
        info[3] = info[3] == 1                      # Is output
        info[4] = info[4] == 1                      # Is opened (bool)
        devices.append(info)
    return devices


def initialize():
    pypm.Initialize()


def terminate():
    pypm.Terminate()


class MidiEvent:

    def __init__(self, status=0, timestamp=0, data=[]):
        self.status = status            # int
        self.timestamp = timestamp      # int
        self.data = data                # list

    def toString(self):
        return str(self.status) + ";" + str(self.timestamp) + ";" + str(self.data)

    def fromString(self, description):
        description = description.split(";")
        self.status = int(description[0])
        self.timestamp = int(description[1])
        self.data = literal_eval(description[2])

    def __eq__(self, other):
        return type(other) is type(self) and self.__dict__ == other.__dict__

    def __ne__(self, other):
        return not self.__eq__(other)


class MidiEventHandler(QObject):

    '''Emit a MidiEvent object only in non-recordMode'''
    midiReaded = pyqtSignal(object)

    '''Emit a MidiEvent object only in recordMode'''
    midiRecorded = pyqtSignal(object)

    def __init__(self, device=-1, freq=0.125):
        super(MidiEventHandler, self).__init__()

        if(pypm.CountDevices() < 0):
            raise Exception("No MIDI device found!")

        self.__running = False
        self.__handlerThread = None
        self.__input = None
        self.__channels = pypm.Channel(1)

        if(device < 0):
            self.device = pypm.GetDefaultInputDeviceID()
        else:
            self.device = device

        self.recordMode = False
        self.freq = freq

        self.reset()

    def reset(self, restart=True):
        self.stop()

        if(self.__input != None):
            self.__input.Close()

        self.__input = pypm.Input(self.device)

        if(restart):
            self.start()

    def changeDevice(self, device):
        self.device = device
        self.reset()

    def changeChannels(self, channels):
        mask = pypm.Channel(channels[0])
        for channel in channels[1:]:
            mask |= pypm.Channel(channel)
        self.__input.SetChannelMask(mask)

    def changeChannel(self, channel):
        self.__input.SetChannelMask(pypm.Channel(channel))

    def start(self):
        self.__handlerThread = Thread(target=self.__run)
        self.__handlerThread.setDaemon(True)
        self.__running = True
        self.__handlerThread.start()

    def stop(self):
        self.__running = False

    def __run(self):
        while(self.__running):
            while(self.__input.Poll() == 1):
                readed = self.__input.Read(1)
                event = MidiEvent(readed[0][0][0],      # status
                                  readed[0][1],         # timestamp
                                  [readed[0][0][1],     # data1
                                   readed[0][0][2],     # data2
                                   readed[0][0][2]])    # data3
                if(self.recordMode):
                    self.midiRecorded.emit(event)
                else:
                    self.midiReaded.emit(event)
            sleep(self.freq)
        self.reset(False)
